package be.kdg.bombermanunlimited.server.remote.finalclasses;

import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
import java.util.ArrayList;
import java.util.List;

import be.kdg.bombermanunlimited.client.remote.interfaces.IGameListener;
import be.kdg.bombermanunlimited.server.map.Map;
import be.kdg.bombermanunlimited.server.network.interfaces.IGameRequests;
import be.kdg.bombermanunlimited.server.objects.finalclasses.Bomb;
import be.kdg.bombermanunlimited.server.objects.finalclasses.Player;
import be.kdg.bombermanunlimited.server.objects.interfaces.IBomb;
import be.kdg.bombermanunlimited.server.objects.interfaces.IMap;
import be.kdg.bombermanunlimited.server.objects.interfaces.IPlayer;
import be.kdg.bombermanunlimited.server.objects.interfaces.IPowerup;
import be.kdg.bombermanunlimited.server.remote.interfaces.IGame;
import be.kdg.bombermanunlimited.server.remote.interfaces.IServerGameListener;

/**
 * draaiende gamelogica op de server, alle oproepen van clients die nieuwe spelers willen aanmaken gebeurt via deze klasse.
 * het wordt als RMI-object aangemaakt wanneer de server opgezet wordt.
 * @author pieter
 *
 */
public class Game extends UnicastRemoteObject implements IGame, IGameRequests {

	private static final long serialVersionUID = 1L;
	private List<IPlayer> players;
	private List<IBomb> bombs;
	private List<IPowerup> powerups;
	private IMap map;
	private int mapsize = 21;
	private final int maxPlayers = 3;

	public Game() throws RemoteException {
		super();
		players = new ArrayList<IPlayer>();
		bombs = new ArrayList<IBomb>();
		powerups = new ArrayList<IPowerup>();
		map = new Map(mapsize, 20);
	}

	/**
	 * maakt een player opbject aan. er is een gameListener meegestuurd. dit
	 * dient om later terug naar de client te communiceren. deze wordt als
	 * "client" bijgehouden in de respectievelijke Player nadat de overige
	 * spelers worden doorgestuurd naar de client wordt de nieuwe speler
	 * toegevoegd aan de lokale spelerslijst op de server
	 */
	public IPlayer addPlayer(String name, IGameListener client)
			throws RemoteException {
		System.out.println("Add Player, nam: " + name);
		IServerGameListener server = new ServerGameListener();
		IPlayer player = new Player(name,
				map.toRow(map.getSpawn(players.size())), map.toColumn(map
						.getSpawn(players.size())), client, server);
		player.getClient().setPlayers(players);

		for (int i = 0; i < players.size(); i++) {
			players.get(i).getClient().addPlayer(player);
		}
		players.add(player);
		System.out.println(players.size());
		for (int i = 0; i < players.size(); i++) {
			System.out.println(players.get(i).getName()
					+ players.get(i).getRow() + players.get(i).getColumn());
		}
		player.getClient().setMap(map.getTiles());
		return player;
	}

	@Override
	public IMap getMap() throws RemoteException {
		return map;
	}

	/**
	 * elke client krijgt de opdracht om het spel te starten. 
	 * in een oneindig while lus wordt er constant naar ontploffingen gezocht.
	 * de rest van updates wordt afgehandeld door threads die door de client worden opgestart.
	 */
	public void start() throws RemoteException {
		try {
			Thread.sleep(5000);
		} catch (InterruptedException e1) {
		}

		System.out.println("Game started!");
		for (IPlayer player : players) {
			player.getClient().startGame();
		}

		while (true) {
			checkForExplosions();
			try {
				Thread.sleep(20);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}

	/**kijken of er een bom op ontploffen staat */
	private void checkForExplosions() {
		boolean next = true;
		while (next == true && bombs.size() > 0) {
			if (bombs.get(0).explode()) {
				System.out.println(" bom ontploft!");
				for (int i = 0; i < players.size(); i++) {
					try {
						players.get(i).getClient().blowBomb();
					} catch (RemoteException e) {
						e.printStackTrace();
					}
				}
				checkForHits();
				bombs.remove(0);
			} else {
				next = false;
			}
		}
	}

	/** hitdetectie. van middelpunt, in 4 richtingen, 4 for-lussen*/
	private void checkForHits() {
		if (bombs.size() > 0) {
			int row = bombs.get(0).getRow();
			int column = bombs.get(0).getColumn();
			int range = bombs.get(0).getRange();
			boolean blowLeft, blowRight, blowUp, blowDown;
			System.out.println("checkForHits");

			// kijken of er in de range onder de bom iets of iemand staat
			for (int i = row; i <= row + range; i++) {
				try {
					if ((map.isWalkable(i, column))) {
						// check of er een player staat
						List<IPlayer> deadPlayers = FindHit(i, column);
						for (int j = 0; j < deadPlayers.size(); j++) {
							RemovePlayer(deadPlayers.get(j));
						}
					} else {
						if (map.isBlowable(i, column)) {
							deleteBlowable(i, column);
						}
						break;
					}
				} catch (RemoteException e) {
					e.printStackTrace();
				}
			}

			// kijken of er in de range boven de bom iets of iemand staat
			for (int i = row; i >= row - range; i--) {
				try {
					if ((map.isWalkable(i, column))) {
						// check of er een player staat
						List<IPlayer> deadPlayers = FindHit(i, column);
						for (int j = 0; j < deadPlayers.size(); j++) {
							RemovePlayer(deadPlayers.get(j));
						}

					} else {
						if (map.isBlowable(i, column)) {
							deleteBlowable(i, column);
						}
						break;
					}
				} catch (RemoteException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
			// kijken of er in de range rechts van de bom iets of iemand staat
			for (int i = column; i <= column + range; i++) {
				try {
					if ((map.isWalkable(row, i))) {
						// check of er een player staat
						List<IPlayer> deadPlayers = FindHit(row, i);
						for (int j = 0; j < deadPlayers.size(); j++) {
							RemovePlayer(deadPlayers.get(j));
						}

					} else {
						if (map.isBlowable(row, i)) {
							deleteBlowable(row, i);
						}
						break;
					}
				} catch (RemoteException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}

			// kijken of er in de range links van de bom iets of iemand staat
			for (int i = column; i >= column - range; i--) {
				try {
					if ((map.isWalkable(row, i))) {
						// check of er een player staat
						List<IPlayer> deadPlayers = FindHit(row, i);
						for (int j = 0; j < deadPlayers.size(); j++) {
							RemovePlayer(deadPlayers.get(j));
						}

					} else {
						try {
							if (map.isBlowable(row, i)) {
								deleteBlowable(row, i);
							}
						} catch (RemoteException e) {
							// TODO Auto-generated catch block
							e.printStackTrace();
						}
						break;
					}
				} catch (RemoteException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		}
	}

	// kijken of er een player op die positie staat, array van spelers vullen
	// die geraakt zijn
	private List<IPlayer> FindHit(int row, int column) {
		List<IPlayer> temp = new ArrayList<IPlayer>();
		for (int i = 0; i < players.size(); i++) {
			try {
				if (players.get(i).getRow() == row
						&& players.get(i).getColumn() == column) {
					temp.add(players.get(i));
				}
			} catch (RemoteException e) {
				e.printStackTrace();
			}
		}
		return temp;

	}

	private void RemovePlayer(IPlayer player) {
		try {
			player.getClient().gameOver();
		} catch (RemoteException e1) {
			e1.printStackTrace();
		}
		try {
			player.setDead();
		} catch (RemoteException e1) {
			// TODO Auto-generated catch block
			e1.printStackTrace();
		}
		int index = players.indexOf(player);
		int j = 1;
		for (int i = 0; i < players.size(); i++) {
			if (!(players.get(i).equals(player))) {
				try {
					players.get(i).getClient().setDead(index - j);
				} catch (RemoteException e) {
					e.printStackTrace();
				}
			} else {
				j = 0;
			}
		}
	}

	// elke player zijn map moet geupdated worden+ de local map op de server
	// ook.
	private void deleteBlowable(int row, int column) {
		for (int i = 0; i < players.size(); i++) {
			try {
				map.changeTile(map.toIndex(row, column));
				players.get(i).getClient().changeTile(map.toIndex(row, column));
			} catch (RemoteException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}

	public List<IPlayer> getPlayers() throws RemoteException {
		System.out.println("Getting players");
		return players;
	}

	public int getPlayerCount() throws RemoteException {
		return players.size();
	}

	/*
	 * stuurt naam row en column van elke overige speler door naar elke speler
	 */
	public synchronized void updateAllPlayers() throws RemoteException {

		for (int j = 0; j < players.size(); j++) {

			int i = 0;
			int[] row = new int[players.size() - 1];
			int[] collumn = new int[players.size() - 1];
			String[] name = new String[players.size() - 1];
			for (int k = 0; k < players.size(); k++) {
				if (j != k) {
					try {
						row[i] = players.get(k).getRow();
						collumn[i] = players.get(k).getColumn();
						name[i] = players.get(k).getName();
					} catch (RemoteException e) {
						e.printStackTrace();
					}
					i++;
				}
			}
			try {
				players.get(j).getClient().updatePlayers(name, row, collumn);
			} catch (RemoteException e) {
				e.printStackTrace();
			}
		}
	}

	/*
	 * deze innerclass wordt gebruik als listener om de communicatie tussen een
	 * speler op de server en de gamelogica in deze klasse te vergemakkelijken.
	 * wordt gebruikt voor playerupdates, bommen die gelegd worden etc.
	 */
	public class ServerGameListener implements IServerGameListener {

		public boolean isWalkable(int row, int column) {
			if (row < 0 || row >= mapsize || column < 0 || column >= mapsize) {
				return false;
			}

			for (int i = 0; i < bombs.size(); i++) {
				if (bombs.get(i).getRow() == row
						&& bombs.get(i).getColumn() == column) {
					return false;
				}
			}
			try {
				return (map.isWalkable(row, column));
			} catch (RemoteException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			return true;
		}

		public void Update(IPlayer player) {
			int j = 0;
			for (int i = 0; i < players.size(); i++) {
				if (!(players.get(i).equals(player))) {
					try {
						players.get(i).getClient().updatePlayer(i - j, player);
					} catch (RemoteException e) {
						e.printStackTrace();
					}
				} else {
					j = 1;
				}
			}
		}

		public void addBomb(int row, int column, int range) {
			bombs.add(new Bomb(row, column, range));
			System.out.println("bomb added! bombcount:" + bombs.size());
			for (int i = 0; i < players.size(); i++) {
				try {
					players.get(i).getClient().addBomb(row, column, range);
				} catch (RemoteException e) {
					e.printStackTrace();
				}
			}

		}

	}

	@Override
	public boolean isFull() {
		return players.size() == maxPlayers;
	}
}
